TypeScript 的小技巧总结和知识点补充
TypeScript 中常用的关键字
typeof
原生 JavaScript 中也具有该关键字,可以判断一个数据的类型,最终返回对应的字符串类型,比如 "number"
、"string"
...
在 TypeScript 中增强了 typeof
的功能,对于复杂类型的数据,typeof
也可以返回,并且可以赋值给类型别名。
interface Person {
name: string;
age: number;
}
const sem: Person = { name: "semlinker", age: 30 };
type Sem = typeof sem; // type Sem = Person
甚至可以获取一个函数的类型。
function toArray(x: number): Array<number> {
return [x];
}
type Func = typeof toArray; // -> (x: number) => number[]
important
一定要记住 typeof
后面接的一定是一个 具体的数据,比如一个变量或者一个属性。
有时候你可能还会发现自己想要定义一个类型来匹配一个初始配置对象的「形状」,比如:
const INIT_OPTIONS = {
width: 640,
height: 480,
color: "#00FF00",
label: "VGA",
};
这时使用 typeof
就很方便获取对象的类型。
type Options = typeof INIT_OPTIONS;
/*
equal:
type Options = {
width: number,
height: number,
color: string,
label: string
};
*/
keyof
该操作符可以获取某种类型中所有的键,其返回的类型是联合类型。
最常用的就是获取一个对象类型中所有的键。
interface Person {
name: string;
age: number;
}
type KeyOfPerson = keyof Person; // "name" | "age"
如果是 keyof Person[]
,本质上获取的是数组类型的所有键。
type K = typeof Person[]; // "length" | "toString" | "pop" | "push" | "concat" | "join" ...
important
keyof
后面一定接一个 类型,并且返回的是 字面量联合类型。
in
该操作符可以用来遍历字符串字面量、数字和 symbol 的联合类型。
type Keys = "a" | "b" | "c";
type Obj = {
[p in Keys]: any;
}; // -> { a: any, b: any, c: any }
extends 与 infer
对象类型的 & 和 | 运算
先看一个简单的例子。
type P = { x: number } & { y: number };
// 等价于 {x: number, y: number}
此时符合 P 接口的对象必须同时包含 x 和 y 两个属性,并且都为 number 类型。
type P = { x: number } | { y: number };
// 等价于 {x: number} | {y: number} | {x: number, y: number}
此时符合 P 接口的对象可以只有 x 或 y 其中一个属性,也可以同时具有 x 和 y 两个属性。
另外,如果对象类型在运算过程中发生属性冲突,即属性名相同类型不同,冲突属性的类型也会进行相应运算。
type P = { x: number; id: number } & { y: number; id: string };
// 等价于 {x: number, y: number, id: never}
这里 id 属性发生冲突,进行运算后变成 id: number & string
,不可能存在既是 number 类型又是 string 类型的值,所以 id 属性变为 never 类型。
type P = { x: number; id: number } | { y: number; id: string };
// 等价于 {x: number, id: number} | {y: number, id: string} | {x: number, y: number, id: number | string}
id 属性发生冲突后,运算后变为 id: number | string
。
最后来看一个复杂点的例子。
type P = { x: number; id?: number } | { y: number; id?: string };
// 等价于 {x: number, id: number | undefined} | {y: number, id: string | undefined} | {x: number, y: number, id: number | string | undefined}
这里的 id 属性由于可以为 undefined,因此我们可以缺省这个属性。
let p1: P = { x: 1 }; // Ok
let p2: P = { x: 1, y: 2 }; // Ok
下面来看 & 运算。
type P = { x: number; id?: number } & { y: number; id?: string };
// 等价于 {x: number, y: number, id: undefined}
运算过程中 id 属性冲突,进行对应的运算相当于 (number | undefined) & (string | undefined)
,最终运算结果就是 undefined
。
important
如果是接口在 extends
过程中发生属性冲突,那么会直接报错。
索引访问
对象类型的索引访问
我们可以使用 索引访问 的机制来获取对象类型中某个属性的类型。
interface Person {
name: string;
age: number;
}
type NameType = Person["name"]; // string 类型
type AgeType = Person["age"]; // number 类型
type KeysType = Person["name" | "age"]; // string | number 联合类型
// equal
type KeysType = Person[keyof Person];
这里的 Person 类型也可以由类型别名 type
来定义,效果是一样的。
important
keyof
用于获取一个类型的所有键,以字符串字面量的联合类型进行返回。
数组类型的索引访问
interface Person {
name: string;
age: number;
}
// 等价于 type PersonArray = Person[];
interface PersonArray {
[P: number]: Person;
}
此时如果想要获取数组类型 PersonArray
的元素类型:
type P = PersonArray[number]; // type P = Person
模版字面量类型
在原生 JS 中有模版字符串语法,在 TS 里面对于字面量类型也可以使用这种语法,并且功能更加强大。
type World = "world";
type Greeting = `hello ${World}`; // type Greeting = "hello world"
你还可以利用模版字符串语法,给字面量联合类型的每一个子类型统一添加前缀或后缀。
type EmailLocaleIDs = "welcome_email" | "email_heading";
type FooterLocaleIDs = "footer_title" | "footer_sendoff";
type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`;
// type AllLocaleIDs = "welcome_email_id" | "email_heading_id" | "footer_title_id" | "footer_sendoff_id"
详情参考 Template Literal Types。
映射类型属性
in
和 typeof
关键字搭配使用可以遍历一个对象类型中的所有属性。
type OptionsFlags<Type> = {
[Property in keyof Type]: boolean;
};
OptionsFlags
的作用就是将泛型变量 Type
中的所有属性设置为 boolean
类型。
在这过程中你可以使用 +
或者 -
,来设置属性是否为只读属性或者可选属性。
type OptionsFlags<Type> = {
+readonly [Property in keyof Type]+?: boolean;
};
此时 OptionsFlags
的作用是将泛型变量 Type
中的所有属性设置为只读和可选属性,并且都为 boolean
类型。
详情参考 Mapped Types
练习题
任意字面量类型
type LiteralType = keyof any; // type LiteralType = string | number | symbol;
扁平化数组类型
要求:泛型参数可以为任意类型,如果是数组类型则返回该数组的元素类型,否者不进行处理即返回传入的泛型参数类型。
type Flatten<T> = T extends unknown[] ? T[number] : T;
T[number]
表示的就是数组类型中的元素类型,利用的是类型索引机制,因为数组类型里面的 key 是 0、1、2 ... 从 0 开始的递增的数字下标。
使用 infer
改进。
type Flatten<T> = T extends Array<infer R> ? R : T;
映射字符串数组中元素作为字面量联合类型
const colorTypes = [
"blue",
"green",
"yellow",
"orange",
"pink",
"red",
"purple",
"gray",
];
// 要求得到
type Color = ToUnionString<typeof colorTypes>; // "blue" | "green" | "yellow" ...